#include "documentlist.h"

/** 
 * This file is part of uhferret.
 *
 * Author::    Peter Lane
 * Copyright:: Copyright 2011, Peter Lane.
 * License::   GPLv3
 *
 * uhferret is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * uhferret is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with uhferret.  If not, see <http://www.gnu.org/licenses/>.
 */

DocumentList::DocumentList () : 
  _last_group_id (0) 
{}

DocumentList::~DocumentList ()
{
	Clear ();
}

void DocumentList::AddDocument (std::string pathname, Document::DocumentType type)
{
	_documents.push_back (new Document (pathname, type, GetNewGroupId ()));
}

void DocumentList::AddDocument (std::string pathname, Document::DocumentType type, int id)
{
	_documents.push_back (new Document (pathname, type, id));
}

Document * DocumentList::getDocument (std::size_t i) const
{
	assert (i >= 0 && i < _documents.size ());
	return _documents[i];
}

void DocumentList::RemoveDocument (Document * doc)
{
	for (std::vector<Document *>::iterator it = _documents.begin();
		it != _documents.end();
		++it)
	{
		if (*it == doc)
		{
			_documents.erase (it);
			return;
		}
	}
}

TokenSet & DocumentList::GetTokenSet ()
{
	return _token_set;
}

TupleSet & DocumentList::GetTupleSet ()
{
	return _tuple_set;
}

// A Document list owns the documents, so is responsible for deleting them
void DocumentList::Clear ()
{
	for (int i=0, n=_documents.size(); i<n; ++i)
	{
		delete _documents[i];
	}
	_documents.clear ();
	ResetReading ();
}

// return a new, unique group id.  
// note that id = 0 has special meaning (files downloaded from web)
int DocumentList::GetNewGroupId ()
{
	_last_group_id += 1;
	return _last_group_id;
}

void DocumentList::ResetReading ()
{
	_token_set.Clear ();
	_tuple_set.Clear ();
	_matches.clear ();
}

int DocumentList::Size () const
{
	return _documents.size ();
}

// don't count pairs of documents in same group
int DocumentList::NumberOfPairs () const
{
	int num_pairs = 0;
	for (int i = 0; i < _documents.size (); ++i)
		for (int j = i+1; j < _documents.size (); ++j)
		{
			if (_documents[i]->GetGroupId () != _documents[j]->GetGroupId ())
				num_pairs++;
		}
	return num_pairs;
}

void DocumentList::RunFerret (int first_document)
{
  ResetReading ();
	// phase 1 -- read each file in turn, finding trigrams
	for (int i = first_document; i < _documents.size (); ++i)
	{
		ReadDocument (i);
	}

	// phase 2 -- compute the similarities
	ComputeSimilarities ();
}

void DocumentList::ReadDocument (int i)
{
	_documents[i]->StartInput (_token_set);
	_documents[i]->ResetTrigramCount ();
	while ( _documents[i]->ReadTrigram (_token_set) )
	{
		if (_tuple_set.AddDocument (
					_documents[i]->GetToken (0),
					_documents[i]->GetToken (1),
					_documents[i]->GetToken (2),
					i))
		{
			_documents[i]->IncrementTrigramCount ();
		}
	}
	_documents[i]->CloseInput ();
}

void DocumentList::ClearSimilarities ()
{
	for (int i=0; i < _documents.size() * _documents.size(); ++i)
	{
		_matches.push_back (0);
	}
}

void DocumentList::ComputeSimilarities ()
{
	ClearSimilarities ();
	for (_tuple_set.Begin (); _tuple_set.HasMore (); _tuple_set.GetNext ())
	{
		const std::vector<int> & fvector = _tuple_set.GetDocumentsForCurrentTuple ();
		// take each pair of documents in the vector, and add one to matches
		for (unsigned int fi = 0, n = fvector.size (); fi < n; ++fi)
		{
			for (unsigned int fj=fi+1; fj < n; ++fj)
			{
				// ensure that first index is smaller than the second
				int doc1 = fvector[(fi <= fj ? fi : fj)];
				int doc2 = fvector[(fi <= fj ? fj : fi)];
				assert (doc1 * _documents.size() + doc2 < _matches.size());
				_matches[doc1 * _documents.size() + doc2]++;
			}
		}
	}
}

int DocumentList::GetTotalTrigramCount ()
{
	return _tuple_set.Size ();
}

int DocumentList::CountTrigrams (int doc_i) 
{
	return _documents[doc_i]->GetTrigramCount ();
}

int DocumentList::CountMatches (int doc_i, int doc_j)
{
	assert (doc_j > doc_i); // _matches is only completed from one side, with doc_j > doc_i
	assert ((doc_i * _documents.size() + doc_j) < _matches.size());
	return _matches[doc_i * _documents.size() + doc_j];
}

float DocumentList::ComputeResemblance (int doc_i, int doc_j)
{
	float num_matches = (float)CountMatches (doc_i, doc_j);
	float total_trigrams = (float)(CountTrigrams (doc_i) + CountTrigrams (doc_j) - CountMatches (doc_i, doc_j));
	if (total_trigrams == 0.0) return 0.0; // check for divide by zero
	return num_matches/total_trigrams;
}

float DocumentList::ComputeContainment (int doc_i, int doc_j)
{
	float num_matches = (float)(doc_j > doc_i ? CountMatches (doc_i, doc_j) : CountMatches (doc_j, doc_i));
	float target_trigrams = (float)(CountTrigrams (doc_j));
	if (target_trigrams == 0.0) return 0.0; // check for divide by zero
	return num_matches/target_trigrams;
}

bool DocumentList::IsMatchingTrigram (std::size_t t0, std::size_t t1, std::size_t t2, int doc1, int doc2)
{
	return _tuple_set.IsMatchingTuple (t0, t1, t2, doc1, doc2);
}

std::string DocumentList::MakeTrigramString (std::size_t t0, std::size_t t1, std::size_t t2)
{
	std::string tuple = "";
	tuple += _token_set.GetStringFor (t0);
	tuple += " " + _token_set.GetStringFor (t1);
	tuple += " " + _token_set.GetStringFor (t2);
	
	return tuple;
}

std::vector<std::string> DocumentList::CollectMatchingTrigrams (int doc1, int doc2) 
{
	return _tuple_set.CollectMatchingTuples (doc1, doc2, _token_set);
}

